const express = require('express');
const bodyParser = require('body-parser');
const multiparty = require('multiparty');
const SparkMd5 = require('spark-md5');
const fs = require('fs');
const cors = require('cors');



const app = express(),
  PORT = 8888,
  HOST = 'http://127.0.0.1',
  HOSTNAME = `${HOST}:${PORT}`;

app.use(express.static('./'))

app.use((req, res, next) => {
  console.log('req.method', req.method)
  res.header('Access-Control-Allow-Origin', '*')

  req.method === 'OPTIONS' ? res.send('current services support cross domain requests') : next()
})
// app.use(cors)

app.use(bodyParser.urlencoded({
  extended: false,
  limit: '1024mb'
}))

// 延迟函数
const delay = (interval) => {
  typeof interval !== 'number' ? interval = 1000  : null
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, interval);
  })
}

// 检测文件是否存在
const isFileExist = (path) => {
  return new Promise(resolve => {
    fs.access(path, fs.constants.F_OK, err => {
      if (err) {
        console.log('isFileExist', err)
        resolve(false)
      }
      resolve(true)
    })
  })
}

// 创建文件并写入到指定目录 & 返回客户端结果
const writeFile = (res, path, file, filename, stream) => {
  console.log('path, file, filename', path, file, filename)
  const write = (path, data, resolve) => {
    fs.writeFile(path, data, err => {
      if (err) {
        reject(err)
        res.send({
          code: 1,
          codeText: err
        })
        return
      }
      resolve()
      res.send({
        code: 0,
        codeText: '上传成功',
        originFilename: filename,
        servicePath: path.replace(__dirname, HOSTNAME)
      })
    })
  }
  return new Promise((resolve, reject) => {
    if (stream) { // file object 类型
      fs.readFile(file.path, (err, data) => {
        if (err) {
          reject(err)
          res.send({
            code: 1,
            codeText: err
          })
          return
        }
        write(path, data, resolve)
        return
      })
    }else {
      write(path, file, resolve)
    }
  })
}

// 基于multiparty插件实现文件上传处理 & form-data 解析
const uploadDir = `${__dirname}/upload`
const multiparty_upload = (req, auto) => {
  typeof auto !== 'boolean' ? auto = false : null
  let config = {
    maxFieldSize: 200 * 1024 * 1024
  }
  if (auto) {
    config.uploadDir = uploadDir
  }
  return new Promise(async (resolve, reject) => {
    await delay()
    new multiparty.Form(config).parse(req, (err, fields, files) => {
      if (err) {
        reject(err)
        return
      }else {
        resolve({fields, files})
      }
    })
  })
}

// 单文件上传 form-data
app.post('/upload_single', async (req,res) => {
  try {
    let { files, fields } = await multiparty_upload(req, true)
    let file = (files.file && files.file[0]) || {}
    console.log('files', files)
    res.send({
      code: 0,
      codeText: '上传成功',
      originFilename: file.originFilename,
      servicePath: file.path.replace(__dirname, HOSTNAME)
    })
  } catch (error) {
    res.send({
      code: 1,
      codeText: error
    })
  }
})


app.post('/upload_single_name', async (req,res) => {
  try {
    const { fields, files } = await multiparty_upload(req) // 只解析数据
    let file = (files.file && files.file[0]) || {},
        filename = (fields.filename && fields.filename[0]) || '',
        path = `${uploadDir}/${filename}`,
        isExist = false;

    isExist = await isFileExist(path)
    // 检测是否存在
    if (isExist) {
      res.send({
        code: 0,
        codeText: '文件已经存在~',
        originFilename: filename,
        servicePath: path.replace(__dirname, HOSTNAME)
      })
      return
    }
    console.log('upload_single_name', path, file, filename);
    /*
      path, file, filename
      E:\frontEndsProgramming\JavaScript\upload-file\server/upload/d4531ffea6e50cff8c738a4db732aad5.png
      {
        fieldName: 'file',
        originalFilename: 'ts.png',
        path: 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\MJ8AQ2i3Kew8tc0NhO-V5K_M.png',
        headers: {
          'content-disposition': 'form-data; name="file"; filename="ts.png"',
          'content-type': 'image/png'
        },
        size: 2776
      }
      d4531ffea6e50cff8c738a4db732aad5.png
    */
    writeFile(res, path, file, filename, true)

  } catch (error) {
    res.send({
      code: 1,
      codeText: error
    })
  }
})

// 单文件上传 base64
app.post('/upload_single_base64', async (req,res) => {
  let file = req.body.file,
        filename = req.body.filename,
        spark = new SparkMd5.ArrayBuffer(),  // 根据文件内容生成 hash code
        suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1],
        isExist = false,
        path;
  file = decodeURIComponent(file) // 解码
  file = file.replace(/^data:image\/\w+;base64,/, '')
  file = Buffer.from(file, 'base64') // base64 格式转换成 buffer格式
  spark.append(file)
  path = `${uploadDir}/${spark.end()}.${suffix}`
  await delay()
  isExist = await isFileExist(path)
  if (isExist) {
    res.send({
      code: 0,
      codeText: '文件已经存在~',
      originFilename: filename,
      servicePath: path.replace(__dirname, HOSTNAME)
    })
    return
  }
  console.log('upload_single_base64', path, file, filename);
  // upload_single_base64 E:\frontEndsProgramming\JavaScript\upload-file\server/upload/d4531ffea6e50cff8c738a4db732aad5.png
  // <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0 00 ... 2726 more bytes>
  // ts.png
  writeFile(res, path, file, filename, false)
})

function merge(HASH, count) {
  return new Promise(async (resolve, reject) => {
    let path = `${uploadDir}/${HASH}`,
        fileList = [],
        suffix,
        isExist;

    isExist = await isFileExist(path)
    if (!isExist) {
      reject('HASH path is not found')
      return
    }
    fileList = fs.readdirSync(path)
    if (fileList.length < count) {
      reject('this slice has not been uploaded')
      return
    }
    fileList.sort((a, b) => {
      const reg = /_(\d+)/
      return reg.exec(a)[1] - reg.exec(b)[1]
    }).forEach(item => {
      !suffix ? suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1] : null
      fs.appendFileSync(`${uploadDir}/${HASH}.${suffix}`, fs.readFileSync(`${path}/${item}`))
      fs.unlinkSync(`${path}/${item}`)
    })
    fs.rmdirSync(path)
    resolve({
      path: `${uploadDir}${HASH}.${suffix}`,
      filename: `${HASH}/${suffix}`
    })
  })
}
// 大文件切片上传 & 合并切片
app.post('/upload_chunk', async (req,res) => {
  try {
    const { fields, files } = await multiparty_upload(req)
    let file = (files.file && files.file[0]) || {},
          filename = (fields.filename && fields.filename[0]) || '',
          path = ``,
          isExist = false;

    // 创建切片临时目录
    let [, HASH] = /^([^_]+)_(\d+)/.exec(filename)
    path = `${uploadDir}/${HASH}`
    if (!fs.existsSync(path)) {
      fs.mkdirSync(path)
    }
    // 把切片存放到临时目录
    path = `${uploadDir}/${HASH}/${filename}`


    isExist = await isFileExist(path)
    // 检测是否存在
    if (isExist) {
      res.send({
        code: 0,
        codeText: '文件已经存在~',
        originFilename: filename,
        servicePath: path.replace(__dirname, HOSTNAME)
      })
      return
    }
    writeFile(res, path, file, filename, true)
  } catch (error) {
    res.send({
      code: 1,
      codeText: error
    })
  }
})
app.post('/upload_merge', async (req,res) => {
  let {
    HASH,
    count
  } = req.body
  try {
    let {filename, path} = await merge(HASH, count)
    res.send({
      code: 0,
      codeText: '文件合并成功',
      originFilename: filename,
      servicePath: path.replace(__dirname, HOSTNAME)
    })
  } catch (error) {
    res.send({
      code: 1,
      codeText: error
    })
  }
})
app.get('/upload_already', async (req,res) => {
  let {
    HASH
  } = req.query
  let path = `${uploadDir}/${HASH}`,
      fileList = [];
    console.log('upload_already', HASH, fileList)

  try {
    fileList = fs.readdirSync(path)
    fileList = fileList.sort((a, b) => {
      const reg = /_(\d+)/
      return reg.exec(a)[1] - reg.exec(b)[1]
    })
    res.send({
      code: 0,
      codeText: '',
      fileList
    })
  } catch (error) {
    res.send({
      code: 0,
      codeText: '',
      fileList
    })
  }
})

// 文件流下载
app.post('/download', async (req,res) => {
  let { filename } = req.body
  const filepath = `${uploadDir}/${filename}`

  if (await isFileExist(filepath)) {
    const content = fs.readFileSync(filepath)
    res.setHeader('Content-Type', 'application/octet-stream')
    res.setHeader('Content-Disposition', 'attachment;filename=' + filepath)
    res.send(content)
  } else {
    res.send({
      code: 1,
      codeText: '文件不存在'
    })
  }
})

app.listen(PORT, () => {
  console.log('server is running, port：8888');
})
